﻿using GalaSoft.MvvmLight.Command;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Search;
using JiveMessenger.AvalonEdit;
using JiveMessenger.CustomEventArgs;
using JiveMessenger.Models;
using NLog;
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace JiveMessenger.CustomControls
{
    /// <summary>
    /// Interaction logic for ChatControl.xaml
    /// </summary>
    public partial class ChatControl : UserControl
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private AutoResetEvent _scrollResetEvent = new AutoResetEvent(false);
        private ScrollViewer _scrollViewer;
        private Button _scrollbarEndButton;
        public bool ForceScrollNextLineToEnd;
        private const double Epsilon = 0.01;
        private const double MaxScrolledToEndDistance = 60;
        private object LockObject = new object();
        private DispatcherTimer _resizeTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 5), IsEnabled = false };
        private DateTime LastSentNowWritingUpdate { get; set; }
        private bool IsScrollbarBlinking { get; set; }
        public RelayCommand AddEmoticonCommand { get; private set; }
        public RelayCommand<AddEditEmoticonEventArgs> EditEmoticonCommand { get; private set; }
        public RelayCommand<AddEditEmoticonEventArgs> ClickEmoticonCommand { get; private set; }

        public double ScrollOffset { get; set; }


        public bool AnimateEmoticonListEmoticons
        {
            get { return (bool)GetValue(AnimateEmoticonListEmoticonsProperty); }
            set { SetValue(AnimateEmoticonListEmoticonsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AnimateEmoticonListEmoticons.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AnimateEmoticonListEmoticonsProperty =
            DependencyProperty.Register("AnimateEmoticonListEmoticons", typeof(bool), typeof(ChatControl), new PropertyMetadata(true));



        public bool EnableImagePreview
        {
            get { return (bool)GetValue(EnableImagePreviewProperty); }
            set { SetValue(EnableImagePreviewProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EnableImagePreview.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableImagePreviewProperty =
            DependencyProperty.Register("EnableImagePreview", typeof(bool), typeof(ChatControl), new PropertyMetadata(true));



        public bool EnableGfyCatSupport
        {
            get { return (bool)GetValue(EnableGfyCatSupportProperty); }
            set { SetValue(EnableGfyCatSupportProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EnableGfyCatSupport.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableGfyCatSupportProperty =
            DependencyProperty.Register("EnableGfyCatSupport", typeof(bool), typeof(ChatControl), new PropertyMetadata(true));



        public string GfyCatFormat
        {
            get { return (string)GetValue(GfyCatFormatProperty); }
            set { SetValue(GfyCatFormatProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GfyCatFormat.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GfyCatFormatProperty =
            DependencyProperty.Register("GfyCatFormat", typeof(string), typeof(ChatControl), new PropertyMetadata("mp4"));




        public bool EnableImgurVideoSupport
        {
            get { return (bool)GetValue(EnableImgurVideoSupportProperty); }
            set { SetValue(EnableImgurVideoSupportProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EnableImgurVideoSupport.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableImgurVideoSupportProperty =
            DependencyProperty.Register("EnableImgurVideoSupport", typeof(bool), typeof(ChatControl), new PropertyMetadata(true));



        public string ImgurVideoFormat
        {
            get { return (string)GetValue(ImgurVideoFormatProperty); }
            set { SetValue(ImgurVideoFormatProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ImgurVideoFormat.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ImgurVideoFormatProperty =
            DependencyProperty.Register("ImgurVideoFormat", typeof(string), typeof(ChatControl), new PropertyMetadata("mp4"));



        public TextDocument InputDocument
        {
            get { return (TextDocument)GetValue(InputDocumentProperty); }
            set { SetValue(InputDocumentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InputDocument.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InputDocumentProperty =
            DependencyProperty.Register("InputDocument", typeof(TextDocument), typeof(ChatControl), new PropertyMetadata(null));



        public TextDocument ChatDocument
        {
            get { return (TextDocument)GetValue(DocumentProperty); }
            set { SetValue(DocumentProperty, value); }
        }
        // Using a DependencyProperty as the backing store for Document.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DocumentProperty =
            DependencyProperty.Register("ChatDocument", typeof(TextDocument), typeof(ChatControl), new PropertyMetadata(null, new PropertyChangedCallback(ChatDocumentCallback)));


        public FontFamily ChatFontFamily
        {
            get { return (FontFamily)GetValue(ChatFontFamilyProperty); }
            set { SetValue(ChatFontFamilyProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ChatFontFamily.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ChatFontFamilyProperty =
            DependencyProperty.Register("ChatFontFamily", typeof(FontFamily), typeof(ChatControl), new PropertyMetadata(new FontFamily("Microsoft Sans Serif")));



        public FontStyle ChatFontStyle
        {
            get { return (FontStyle)GetValue(ChatFontStyleProperty); }
            set { SetValue(ChatFontStyleProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ChatFontStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ChatFontStyleProperty =
            DependencyProperty.Register("ChatFontStyle", typeof(FontStyle), typeof(ChatControl), new PropertyMetadata(FontStyles.Normal));



        public FontWeight ChatFontWeight
        {
            get { return (FontWeight)GetValue(ChatFontWeightProperty); }
            set { SetValue(ChatFontWeightProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ChatFontWeight.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ChatFontWeightProperty =
            DependencyProperty.Register("ChatFontWeight", typeof(FontWeight), typeof(ChatControl), new PropertyMetadata(FontWeights.Normal));



        public SolidColorBrush ChatForeground
        {
            get { return (SolidColorBrush)GetValue(ChatForegroundProperty); }
            set { SetValue(ChatForegroundProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ChatForeground.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ChatForegroundProperty =
            DependencyProperty.Register("ChatForeground", typeof(SolidColorBrush), typeof(ChatControl), new PropertyMetadata(Brushes.Black));



        public double ChatFontSize
        {
            get { return (double)GetValue(ChatFontSizeProperty); }
            set { SetValue(ChatFontSizeProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ChatFontSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ChatFontSizeProperty =
            DependencyProperty.Register("ChatFontSize", typeof(double), typeof(ChatControl), new PropertyMetadata(12.0));






        public string StatusText
        {
            get { return (string)GetValue(StatusTextProperty); }
            set { SetValue(StatusTextProperty, value); }
        }
        // Using a DependencyProperty as the backing store for StatusText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StatusTextProperty =
            DependencyProperty.Register("StatusText", typeof(string), typeof(ChatControl), new PropertyMetadata(""));



        public bool IsNudgeButtonVisible
        {
            get { return (bool)GetValue(IsNudgeButtonVisibleProperty); }
            set { SetValue(IsNudgeButtonVisibleProperty, value); }
        }
        // Using a DependencyProperty as the backing store for IsNudgeButtonVisible.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsNudgeButtonVisibleProperty =
            DependencyProperty.Register("IsNudgeButtonVisible", typeof(bool), typeof(ChatControl), new PropertyMetadata(false));



        public ObservableCollection<VisualLineElementGenerator> MessageInputElementGenerators
        {
            get { return (ObservableCollection<VisualLineElementGenerator>)GetValue(ElementGeneratorsProperty); }
            set { SetValue(ElementGeneratorsProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ElementGenerators.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ElementGeneratorsProperty =
            DependencyProperty.Register("MessageInputElementGenerators", typeof(ObservableCollection<VisualLineElementGenerator>), typeof(ChatControl), new PropertyMetadata(null, new PropertyChangedCallback(MessageInputElementGenerators_Callback)));



        public ObservableCollection<IVisualLineTransformer> ConversationLineTransformers
        {
            get { return (ObservableCollection<IVisualLineTransformer>)GetValue(ConversationLineTransformersProperty); }
            set { SetValue(ConversationLineTransformersProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ConversationLineTransformers.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ConversationLineTransformersProperty =
            DependencyProperty.Register("ConversationLineTransformers", typeof(ObservableCollection<IVisualLineTransformer>), typeof(ChatControl), new PropertyMetadata(null, new PropertyChangedCallback(ConversationLineTransformers_Callback)));



        public ObservableCollection<VisualLineElementGenerator> ConversationElementGenerators
        {
            get { return (ObservableCollection<VisualLineElementGenerator>)GetValue(ConversationElementGeneratorsProperty); }
            set { SetValue(ConversationElementGeneratorsProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ConversationElementGenerators.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ConversationElementGeneratorsProperty =
            DependencyProperty.Register("ConversationElementGenerators", typeof(ObservableCollection<VisualLineElementGenerator>), typeof(ChatControl), new PropertyMetadata(null, new PropertyChangedCallback(ConversationElementGenerators_Callback)));



        public bool IsInputTextEnabled
        {
            get { return (bool)GetValue(IsInputTextEnabledProperty); }
            set { SetValue(IsInputTextEnabledProperty, value); }
        }
        // Using a DependencyProperty as the backing store for IsInputTextEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsInputTextEnabledProperty =
            DependencyProperty.Register("IsInputTextEnabled", typeof(bool), typeof(ChatControl), new PropertyMetadata(true));

        public bool IsInputTextBlockFocused
        {
            get { return (bool)GetValue(IsInputTextBlockFocusedProperty); }
            set { SetValue(IsInputTextBlockFocusedProperty, value); }
        }
        // Using a DependencyProperty as the backing store for IsInputTextBlockFocused.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsInputTextBlockFocusedProperty =
            DependencyProperty.Register("IsInputTextBlockFocused", typeof(bool), typeof(ChatControl), new PropertyMetadata(false, new PropertyChangedCallback(IsInputTextBlockFocusedProperty_Callback)));

        public ObservableCollection<JiveCustomEmoticonModel> CustomEmoticons
        {
            get { return (ObservableCollection<JiveCustomEmoticonModel>)GetValue(CustomEmoticonsProperty); }
            set { SetValue(CustomEmoticonsProperty, value); }
        }
        // Using a DependencyProperty as the backing store for CustomEmoticons.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CustomEmoticonsProperty =
            DependencyProperty.Register("CustomEmoticons", typeof(ObservableCollection<JiveCustomEmoticonModel>), typeof(ChatControl), new PropertyMetadata(null, new PropertyChangedCallback(CustomEmoticonsPropertyCallback)));

        private static void CustomEmoticonsPropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }


        public ObservableCollection<JiveDefaultEmoticonModel> DefaultEmoticons
        {
            get { return (ObservableCollection<JiveDefaultEmoticonModel>)GetValue(DefaultEmoticonsProperty); }
            set { SetValue(DefaultEmoticonsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DefaultEmoticons.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DefaultEmoticonsProperty =
            DependencyProperty.Register("DefaultEmoticons", typeof(ObservableCollection<JiveDefaultEmoticonModel>), typeof(ChatControl), new PropertyMetadata(null, new PropertyChangedCallback(DefaultEmoticonsPropertyCallback)));

        private static void DefaultEmoticonsPropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }


        ~ChatControl()
        {
            Logger.Debug("ChatControl with ID {0} destructed", this.GetHashCode());

        }


        public ChatControl()
        {
            this.InitializeComponent();
            //if (DesignerProperties.GetIsInDesignMode(this))
            //    return;
            this.EmoticonPopup.DataContext = this;
            this.LastSentNowWritingUpdate = DateTime.MinValue;
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) return;
            AddEmoticonCommand = new RelayCommand(AddEmoticonCommandAction);
            EditEmoticonCommand = new RelayCommand<AddEditEmoticonEventArgs>(EditEmoticonCommandAction);
            ClickEmoticonCommand = new RelayCommand<AddEditEmoticonEventArgs>(ClickEmoticonCommandAction);
            this.SetupAvalon();
            if (ProgramSettings.Instance.HasTouchInput())//make ui elements larger and more touch friendly on tablet
            {
                Style touch = (Style)Resources["ScrollbarStyleTouch"];

                Resources[typeof(ScrollBar)] = touch;

                bnEmoticons.Height = bnEmoticons.Width = 40;
                bnChangeFont.Height = bnChangeFont.Width = 40;
                bnNudge.Height = bnNudge.Width = 40;
                bnSendMessage.Height = 40;
                bnSendMessage.Width = 75;
                RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality);
            }
            else
                RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.NearestNeighbor);

            var scrollViewer = GetDescendantByType(this.AvalonConversation, typeof(ScrollViewer)) as ScrollViewer;
            if (scrollViewer != null)
            {
                _scrollViewer = scrollViewer;
                scrollViewer.ApplyTemplate();
                ScrollBar s = scrollViewer.Template.FindName("PART_VerticalScrollBar", scrollViewer) as ScrollBar;

                if (s != null)
                {
                    var button = GetDescendantByType(s, typeof(Button)) as Button;
                    if (button != null)
                    {
                        _scrollbarEndButton = button;
                    }
                }
            }
        }
        private static void ChatDocumentCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ChatControl cc = d as ChatControl;
            TextDocument doc = e.NewValue as TextDocument;
            if (doc != null)
            {
                doc.UndoStack.SizeLimit = 0;
            }
        }

        private static void ConversationLineTransformers_Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ChatControl cc = d as ChatControl;
            ObservableCollection<IVisualLineTransformer> transformers = e.NewValue as ObservableCollection<IVisualLineTransformer>;
            if (transformers != null)
            {
                transformers.CollectionChanged += cc.AvalonConversationLineTransformers_CollectionChanged;

                cc.AvalonConversation.TextArea.TextView.LineTransformers.Clear();
                foreach (var v in transformers)
                {
                    cc.AvalonConversation.TextArea.TextView.LineTransformers.Add(v);
                    //if (v.GetType() == typeof(LineColorizer))
                    //    cc.AvalonConversation.TextArea.TextView.LineTransformers.Add((LineColorizer)v);
                }
            }
            if (e.OldValue != null)
            {
                ((ObservableCollection<IVisualLineTransformer>)e.OldValue).CollectionChanged -= cc.AvalonConversationLineTransformers_CollectionChanged;
            }
        }

        private static void ConversationElementGenerators_Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ChatControl cc = d as ChatControl;
            ObservableCollection<VisualLineElementGenerator> generators = e.NewValue as ObservableCollection<VisualLineElementGenerator>;
            if (generators != null)
            {
                generators.CollectionChanged += cc.AvalonConversationElementGenerators_CollectionChanged;
                cc.AvalonConversation.TextArea.TextView.ElementGenerators.Clear();
                foreach (var v in generators)
                {
                    cc.AvalonConversation.TextArea.TextView.ElementGenerators.Add(v);
                    //if (v.GetType() == typeof(ImageElementGenerator))
                    //{
                    //    cc.AvalonConversation.TextArea.TextView.ElementGenerators.Add((ImageElementGenerator)v);

                    //}
                    //else if (v.GetType() == typeof(CustomLinkElementGenerator))
                    //{
                    //    cc.AvalonConversation.TextArea.TextView.ElementGenerators.Add((CustomLinkElementGenerator)v);
                    //}
                    //else if (v.GetType() == typeof(ImageZoomLinkElementGenerator))
                    //{
                    //    ImageZoomLinkElementGenerator g = v as ImageZoomLinkElementGenerator;
                    //    cc.AvalonConversation.TextArea.TextView.ElementGenerators.Add(g);
                    //}
                }
            }
            if (e.OldValue != null)
            {
                ((ObservableCollection<VisualLineElementGenerator>)e.OldValue).CollectionChanged -= cc.AvalonConversationElementGenerators_CollectionChanged;
            }
        }
        private void AvalonConversationElementGenerators_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var generator in e.NewItems)
                    {
                        this.AvalonConversation.TextArea.TextView.ElementGenerators.Add(generator as VisualLineElementGenerator);
                        //if (v.GetType() == typeof(ImageElementGenerator))
                        //{
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Add((ImageElementGenerator)v);
                        //}
                        //else if (v.GetType() == typeof(CustomLinkElementGenerator))
                        //{
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Add((CustomLinkElementGenerator)v);
                        //}
                        //else if (v.GetType() == typeof(ImageZoomLinkElementGenerator))
                        //{
                        //    ImageZoomLinkElementGenerator g = v as ImageZoomLinkElementGenerator;
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Add(g);
                        //}
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var generator in e.NewItems)
                    {
                        this.AvalonConversation.TextArea.TextView.ElementGenerators.Remove(generator as VisualLineElementGenerator);
                        //if (v.GetType() == typeof(ImageElementGenerator))
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Remove((ImageElementGenerator)v);
                        //else if (v.GetType() == typeof(CustomLinkElementGenerator))
                        //{
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Remove((CustomLinkElementGenerator)v);
                        //}
                        //else if (v.GetType() == typeof(ImageZoomLinkElementGenerator))
                        //{
                        //    ImageZoomLinkElementGenerator g = v as ImageZoomLinkElementGenerator;
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Remove(g);
                        //}
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    foreach (var generator in e.OldItems)
                    {
                        this.AvalonConversation.TextArea.TextView.ElementGenerators.Remove(generator as VisualLineElementGenerator);

                        //if (v.GetType() == typeof(ImageElementGenerator))
                        //{
                        //    ImageElementGenerator generator = (ImageElementGenerator)v;

                        //    ImageElementGenerator old = (ImageElementGenerator)this.AvalonConversation.TextArea.TextView.ElementGenerators.First(x => x == generator && x.GetType() == typeof(ImageElementGenerator));
                        //    if (old != null)
                        //        old.ClientLines = generator.ClientLines;
                        //}
                        //else if (v.GetType() == typeof(CustomLinkElementGenerator))
                        //{
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Remove((CustomLinkElementGenerator)v);
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Add((CustomLinkElementGenerator)v);
                        //}
                        //else if (v.GetType() == typeof(ImageZoomLinkElementGenerator))
                        //{
                        //    ImageZoomLinkElementGenerator g = v as ImageZoomLinkElementGenerator;
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Remove(g);
                        //    this.AvalonConversation.TextArea.TextView.ElementGenerators.Add(g);
                        //}

                    }
                    foreach (var generator in e.NewItems)
                    {
                        this.AvalonConversation.TextArea.TextView.ElementGenerators.Add(generator as VisualLineElementGenerator);
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    this.AvalonConversation.TextArea.TextView.ElementGenerators.Clear();
                    break;
                default:
                    break;
            }
        }

        private void AvalonConversationLineTransformers_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var v in e.NewItems)
                    {
                        this.AvalonConversation.TextArea.TextView.LineTransformers.Add(v as IVisualLineTransformer);
                        //if (v.GetType() == typeof(LineColorizer))
                        //    this.AvalonConversation.TextArea.TextView.LineTransformers.Add((LineColorizer)v);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var v in e.OldItems)
                    {
                        this.AvalonConversation.TextArea.TextView.LineTransformers.Remove(v as IVisualLineTransformer);
                        //if (v.GetType() == typeof(LineColorizer))
                        //    this.AvalonConversation.TextArea.TextView.LineTransformers.Remove((LineColorizer)v);
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    foreach (var v in e.OldItems)
                    {
                        //if (v.GetType() == typeof(LineColorizer))
                        //{
                        //    LineColorizer colorizer = (LineColorizer)v;

                        //    LineColorizer old = (LineColorizer)this.AvalonConversation.TextArea.TextView.LineTransformers.First(x => x == colorizer && x.GetType() == typeof(LineColorizer));
                        //    if (old != null)
                        //        old.UserInfo = colorizer.UserInfo;
                        //}

                        this.AvalonConversation.TextArea.TextView.LineTransformers.Remove(v as IVisualLineTransformer);

                    }
                    foreach (var v in e.NewItems)
                    {
                        this.AvalonConversation.TextArea.TextView.LineTransformers.Add(v as IVisualLineTransformer);
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    this.AvalonConversation.TextArea.TextView.LineTransformers.Clear();
                    break;
                default:
                    break;
            }
        }

        private void ClickEmoticonCommandAction(AddEditEmoticonEventArgs args)
        {
            string key = "";
            switch (args.Emoticon.Type)
            {
                case EmoticonType.CustomEmoticon:
                    {
                        key = ((JiveCustomEmoticonModel)args.Emoticon).EmoticonShortcut;
                        break;
                    }
                case EmoticonType.DefaultEmoticon:
                    {
                        key = ((string[])((JiveDefaultEmoticonModel)args.Emoticon).EmoticonShortcut).FirstOrDefault();
                        break;
                    }

            }

            this.AvalonMessage.SelectedText = "";
            this.AvalonMessage.Document.Insert(this.AvalonMessage.CaretOffset, key);
            this.EmoticonPopup.IsOpen = false;
            this.AvalonMessage.Focus();
        }

        private void EditEmoticonCommandAction(AddEditEmoticonEventArgs args)
        {
            RaiseEvent(new AddEditEmoticonEventArgs(EditEmoticonButtonClickedEvent, this, args.Emoticon));
        }

        private void AddEmoticonCommandAction()
        {
            RaiseEvent(new RoutedEventArgs(AddEmoticonButtonClickedEvent, this));
        }


        private static void IsInputTextBlockFocusedProperty_Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ChatControl control = d as ChatControl;
            if (DesignerProperties.GetIsInDesignMode(control))
                return;
            Window parentWin = Window.GetWindow(control);
            App.Current.Dispatcher.BeginInvoke(() =>
                {
                    if (parentWin.IsActive)
                    {
                        if ((bool)e.NewValue == true)
                            control.AvalonMessage.Focus();
                        else
                            parentWin.Focus();
                    }
                });
        }

        private static void MessageInputElementGenerators_Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ChatControl cc = d as ChatControl;
            ObservableCollection<VisualLineElementGenerator> generators = e.NewValue as ObservableCollection<VisualLineElementGenerator>;
            if (generators != null)
            {
                generators.CollectionChanged += cc.AvalonMessageElementGenerators_CollectionChanged;
                foreach (var v in generators)
                {
                    cc.AvalonMessage.TextArea.TextView.ElementGenerators.Add(v);
                    //if (v.GetType() == typeof(ImageElementGenerator))
                    //{
                    //    cc.AvalonMessage.TextArea.TextView.ElementGenerators.Add((ImageElementGenerator)v);
                    //}
                    //else if (v.GetType() == typeof(CustomLinkElementGenerator))
                    //{
                    //    cc.AvalonMessage.TextArea.TextView.ElementGenerators.Add((CustomLinkElementGenerator)v);
                    //}
                    //else if (v.GetType() == typeof(ImageZoomLinkElementGenerator))
                    //{
                    //    ImageZoomLinkElementGenerator g = v as ImageZoomLinkElementGenerator;
                    //    cc.AvalonMessage.TextArea.TextView.ElementGenerators.Add(g);
                    //}
                }
            }
            if (e.OldValue != null)
            {
                generators = e.OldValue as ObservableCollection<VisualLineElementGenerator>;
                generators.CollectionChanged -= cc.AvalonMessageElementGenerators_CollectionChanged;
            }
        }

        private void AvalonMessageElementGenerators_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var generator in e.NewItems)
                    {
                        this.AvalonMessage.TextArea.TextView.ElementGenerators.Add(generator as VisualLineElementGenerator);
                        //if (v.GetType() == typeof(ImageElementGenerator))
                        //{
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Add((ImageElementGenerator)v);
                        //}
                        //else if (v.GetType() == typeof(CustomLinkElementGenerator))
                        //{
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Add((CustomLinkElementGenerator)v);
                        //}
                        //else if (v.GetType() == typeof(ImageZoomLinkElementGenerator))
                        //{
                        //    ImageZoomLinkElementGenerator g = v as ImageZoomLinkElementGenerator;
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Add(g);
                        //}
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var generator in e.NewItems)
                    {
                        this.AvalonMessage.TextArea.TextView.ElementGenerators.Add(generator as VisualLineElementGenerator);
                        //if (v.GetType() == typeof(ImageElementGenerator))
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Remove((ImageElementGenerator)v);
                        //else if (v.GetType() == typeof(CustomLinkElementGenerator))
                        //{
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Remove((CustomLinkElementGenerator)v);
                        //}
                        //else if (v.GetType() == typeof(ImageZoomLinkElementGenerator))
                        //{
                        //    ImageZoomLinkElementGenerator g = v as ImageZoomLinkElementGenerator;
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Remove(g);
                        //}
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    foreach (var generator in e.OldItems)
                    {
                        this.AvalonMessage.TextArea.TextView.ElementGenerators.Remove(generator as VisualLineElementGenerator);
                        //if (v.GetType() == typeof(ImageElementGenerator))
                        //{
                        //    ImageElementGenerator generator = (ImageElementGenerator)v;

                        //    ImageElementGenerator old = (ImageElementGenerator)this.AvalonMessage.TextArea.TextView.ElementGenerators.First(x => x == generator && x.GetType() == typeof(ImageElementGenerator));
                        //    if (old != null)
                        //        old.ClientLines = generator.ClientLines;
                        //}
                        //else if (v.GetType() == typeof(CustomLinkElementGenerator))
                        //{
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Remove((CustomLinkElementGenerator)v);
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Add((CustomLinkElementGenerator)v);
                        //}
                        //else if (v.GetType() == typeof(ImageZoomLinkElementGenerator))
                        //{
                        //    ImageZoomLinkElementGenerator g = v as ImageZoomLinkElementGenerator;
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Remove(g);
                        //    this.AvalonMessage.TextArea.TextView.ElementGenerators.Add(g);
                        //}
                    }
                    foreach (var generator in e.NewItems)
                    {
                        this.AvalonMessage.TextArea.TextView.ElementGenerators.Add(generator as VisualLineElementGenerator);
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    this.AvalonMessage.TextArea.TextView.ElementGenerators.Clear();
                    break;
                default:
                    break;
            }
        }
        #region RegisterEvents

        public event RoutedEventHandler ChangeFontButton_Clicked
        {
            add { AddHandler(ChangeFontEvent, value); }

            remove { RemoveHandler(ChangeFontEvent, value); }
        }

        public static readonly RoutedEvent ChangeFontEvent = EventManager.RegisterRoutedEvent(
"ChangeFontButton_Clicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ChatControl));


        public event RoutedEventHandler OnNowWriting
        {
            add { AddHandler(NowWritingEvent, value); }

            remove { RemoveHandler(NowWritingEvent, value); }
        }
        public static readonly RoutedEvent NowWritingEvent = EventManager.RegisterRoutedEvent(
    "OnNowWriting", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ChatControl));


        public event WriteMessageEventHandler OnSendMessage
        {
            add { AddHandler(SendMessageEvent, value); }

            remove { RemoveHandler(SendMessageEvent, value); }
        }
        public static readonly RoutedEvent SendMessageEvent = EventManager.RegisterRoutedEvent(
            "OnSendMessage", RoutingStrategy.Bubble, typeof(WriteMessageEventHandler), typeof(ChatControl));


        public event RoutedEventHandler OnNudge
        {
            add { AddHandler(NudgeEvent, value); }

            remove { RemoveHandler(NudgeEvent, value); }
        }
        public static readonly RoutedEvent NudgeEvent = EventManager.RegisterRoutedEvent(
    "OnNudge", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ChatControl));


        // Provide CLR accessors for the event
        public event RoutedEventHandler AddEmoticonButtonClicked
        {
            add { AddHandler(AddEmoticonButtonClickedEvent, value); }
            remove { RemoveHandler(AddEmoticonButtonClickedEvent, value); }
        }

        // Using a RoutedEvent
        public static readonly RoutedEvent AddEmoticonButtonClickedEvent = EventManager.RegisterRoutedEvent(
            "AddEmoticonButtonClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ChatControl));



        // Provide CLR accessors for the event
        public event AddEditEmoticonEventHandler EditEmoticonButtonClicked
        {
            add { AddHandler(EditEmoticonButtonClickedEvent, value); }
            remove { RemoveHandler(EditEmoticonButtonClickedEvent, value); }
        }

        // Using a RoutedEvent
        public static readonly RoutedEvent EditEmoticonButtonClickedEvent = EventManager.RegisterRoutedEvent(
            "EditEmoticonButtonClicked", RoutingStrategy.Bubble, typeof(AddEditEmoticonEventHandler), typeof(ChatControl));


        #endregion

        private void SetupAvalon()
        {
            this.AvalonMessage.Options.EnableHyperlinks = false;
            this.AvalonMessage.Options.EnableTextDragDrop = false;
            this.AvalonMessage.Options.EnableEmailHyperlinks = false;
            this.AvalonMessage.Options.RequireControlModifierForHyperlinkClick = false;
            this.AvalonMessage.UseLayoutRounding = true;
            this.AvalonMessage.TextArea.TextView.LinkTextForegroundBrush = (SolidColorBrush)new BrushConverter().ConvertFromString("#0066CC");

            this.AvalonConversation.Options.EnableHyperlinks = false;
            this.AvalonConversation.Options.EnableTextDragDrop = false;
            this.AvalonConversation.Options.RequireControlModifierForHyperlinkClick = false;
            this.AvalonConversation.Options.EnableEmailHyperlinks = false;
            this.AvalonConversation.Options.EnableVirtualSpace = false;
            this.AvalonConversation.TextArea.TextView.LinkTextForegroundBrush = (SolidColorBrush)new BrushConverter().ConvertFromString("#0066CC");
            this.AvalonConversation.UseLayoutRounding = true;

            SearchPanel.Install(this.AvalonConversation);

            foreach (var commandBinding in this.AvalonMessage.TextArea.CommandBindings.Cast<CommandBinding>())
            {
                if (commandBinding.Command == ApplicationCommands.Copy)
                {
                    commandBinding.CanExecute += CopyCommandBinding_CanExecute;
                    commandBinding.Executed += CopyCommandBinding_PreviewExecuted;
                }
                else if (commandBinding.Command == ApplicationCommands.Cut)
                    commandBinding.CanExecute += CopyCommandBinding_CanExecute;
                else if (commandBinding.Command == ApplicationCommands.Paste)
                    commandBinding.CanExecute += PasteCommandBinding_CanExecute;
                else if (commandBinding.Command == ApplicationCommands.Delete)
                    commandBinding.CanExecute += CopyCommandBinding_CanExecute;

            }
            foreach (var commandBinding in this.AvalonConversation.TextArea.CommandBindings.Cast<CommandBinding>())
            {
                if (commandBinding.Command == ApplicationCommands.Copy)
                {
                    commandBinding.CanExecute += CopyCommandBinding_CanExecute;
                    commandBinding.Executed += CopyCommandBinding_PreviewExecuted;
                }
            }
        }


        public void SetScrollbar()
        {
            Logger.Trace("Running SetScrollbar()");

            if (this.AvalonConversation.ExtentHeight < this.AvalonConversation.ViewportHeight)
            {
                ScrollToEnd();
            }
            else if (Math.Abs((this.AvalonConversation.VerticalOffset + this.AvalonConversation.ViewportHeight) - this.AvalonConversation.ExtentHeight) < MaxScrolledToEndDistance || this.ForceScrollNextLineToEnd)
            {
                if (this.AvalonConversation.ExtentHeight > this.AvalonConversation.ViewportHeight)
                {
                    ScrollToEnd();

                }
                StopScrollBlink();

            }
            else if (this.AvalonConversation.ExtentHeight > this.AvalonConversation.ViewportHeight)
            {
                StartScrollBlink();
            }
            this.ForceScrollNextLineToEnd = false;
        }

        private void ScrollToEnd()
        {
            try
            {
                Task.Factory.StartNew(() =>
                    {
                        try
                        {
                            for (int i = 1; i < 20; i++)
                            {
                                this.Dispatcher.Invoke(() =>
                                        {
                                            this.AvalonConversation.ScrollToLine(this.ChatDocument.LineCount);
                                            this.AvalonConversation.TextArea.TextView.VisualLinesChanged += TextView_VisualLinesChanged;
                                            this.AvalonConversation.TextArea.TextView.EnsureVisualLines();
                                        });
                                _scrollResetEvent.WaitOne(TimeSpan.FromMilliseconds(100));
                                bool finished = Math.Abs(this.AvalonConversation.VerticalOffset + this.AvalonConversation.ViewportHeight - this.AvalonConversation.ExtentHeight) < Epsilon;
                                TextView textView = AvalonConversation.TextArea.TextView;
                                if (finished || textView.DocumentHeight < this.AvalonConversation.ViewportHeight)
                                {
                                    Debug.WriteLine(string.Format("Scrolled to end. Scrolled {0} times", i));
                                    break;
                                }
                                i++;
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.ErrorException("Error in ScrollToEnd task", ex);
                        }
                    });
            }
            catch (Exception ex)
            {
                Logger.ErrorException("Error in ScrollToEnd", ex);
            }
        }

        void TextView_VisualLinesChanged(object sender, EventArgs e)
        {
            this.AvalonConversation.TextArea.TextView.VisualLinesChanged -= TextView_VisualLinesChanged;
            _scrollResetEvent.Set();
        }

        void AvalonConversation_LayoutUpdated(object sender, EventArgs e)
        {
            this.AvalonConversation.LayoutUpdated -= AvalonConversation_LayoutUpdated;
            _scrollResetEvent.Set();
        }

        private void StartScrollBlink()
        {
            _scrollbarEndButton.Style = this.FindResource("FavScrollBarEndButtonBlink") as Style;
        }

        private void StopScrollBlink()
        {
            _scrollbarEndButton.Style = this.FindResource("FavScrollBarEndButton") as Style;
        }

        private Visual GetDescendantByType(Visual element, Type type)
        {
            if (element == null)
            {
                return null;
            }
            if (element.GetType() == type)
            {
                return element;
            }
            Visual foundElement = null;
            if (element is FrameworkElement)
            {
                (element as FrameworkElement).ApplyTemplate();
            }
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
            {
                Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
                foundElement = GetDescendantByType(visual, type);
                if (foundElement != null)
                {
                    break;
                }
            }
            return foundElement;
        }

        private void SendMessage(string message)
        {
            ForceScrollNextLineToEnd = true;
            RaiseEvent(new WriteMessageEventArgs(SendMessageEvent, this, this.AvalonMessage.Text));
            StopScrollBlink();
            this.AvalonMessage.Text = string.Empty;
        }


        #region Events
        private void ScrollToEndButton_Click(object sender, RoutedEventArgs e)
        {
            StopScrollBlink();
        }

        private void avalonMessage_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter && e.KeyboardDevice.Modifiers != ModifierKeys.Shift)
            {
                string message = this.AvalonMessage.Text;//this.GetInputText(true);
                if (!String.IsNullOrEmpty(message))
                {
                    SendMessage(message);
                }
                e.Handled = true;
            }
            else if (Keyboard.Modifiers == ModifierKeys.Shift && e.Key == Key.Up)
            {
                if (this.AvalonMessage.LineCount == 1)
                {
                    this.AvalonMessage.SelectAll();
                }
            }
            else if (Keyboard.Modifiers == ModifierKeys.Shift && e.Key == Key.Down)
            {
                if (this.AvalonMessage.LineCount == 1)
                {
                    this.AvalonMessage.Select(this.AvalonMessage.Text.Length, 0);
                }
            }
            else
            {
                if (DateTime.UtcNow.Subtract(this.LastSentNowWritingUpdate).TotalSeconds > 3)
                {
                    RaiseEvent(new RoutedEventArgs(NowWritingEvent, this));
                    this.LastSentNowWritingUpdate = DateTime.UtcNow;
                }
            }
        }

        private void avalonConversation_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (Keyboard.Modifiers != ModifierKeys.Control
                && e.Key != Key.Left
                && e.Key != Key.Right
                && e.Key != Key.Up
                && e.Key != Key.Down
               )
            {
                SearchInputHandler handler = this.AvalonConversation.TextArea.DefaultInputHandler.NestedInputHandlers.FirstOrDefault(x => x is SearchInputHandler) as SearchInputHandler;
                if (handler != null && handler.IsOpen)
                {
                    return;
                }
                this.AvalonMessage.Focus();
            }
        }
        private void AvalonConversation_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            SearchInputHandler handler = this.AvalonConversation.TextArea.DefaultInputHandler.NestedInputHandlers.FirstOrDefault(x => x is SearchInputHandler) as SearchInputHandler;

            if (e.OriginalSource is TextArea)
                handler.IsOpen = false;
        }
        private void bnNudge_Click(object sender, RoutedEventArgs e)
        {
            RaiseEvent(new RoutedEventArgs(NudgeEvent, this));
        }

        /// <summary>
        /// Triggers when clicking bnChangeFont
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bnChangeFont_Click(object sender, RoutedEventArgs e)
        {
            RaiseEvent(new RoutedEventArgs(ChangeFontEvent, this));
        }
        /// <summary>
        /// Triggers when clicking bnEmoticons
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bnEmoticons_Click(object sender, RoutedEventArgs e)
        {
            this.EmoticonPopup.MaxWidth = this.ActualWidth;
            if (this.EmoticonPopup.MaxWidth > 600)
                this.EmoticonPopup.MaxWidth = 600;
            this.EmoticonPopup.IsOpen = true;
        }

        private void CopyCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            TextArea ta = (TextArea)sender;

            e.CanExecute = ta.Selection.Length > 0;
        }

        private void PasteCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = Clipboard.ContainsText();
        }

        private void CopyCommandBinding_PreviewExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            try
            {
                // Get clipboard data and stuff
                var dataObject = Clipboard.GetDataObject();
                if (dataObject != null)
                {
                    var text = (string)dataObject.GetData(DataFormats.UnicodeText);
                    text = text.Replace("<spacer>", "");
                    text = text.Replace("<line>", "");
                    Clipboard.Clear();
                    Clipboard.SetDataObject(text);
                }
            }
            catch (Exception err) { Logger.ErrorException("Error in clipboard", err); }
        }
        private void TextView_ScrollOffsetChanged(object sender, EventArgs e)
        {
            if (!(Math.Abs((this.AvalonConversation.VerticalOffset + this.AvalonConversation.ViewportHeight) - this.AvalonConversation.ExtentHeight) < Epsilon))
                return;
            StopScrollBlink();
        }

        private void CommandBinding_ChangeFontCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.IsInputTextEnabled;
        }

        private void CommandBinding_ChangeFont(object sender, ExecutedRoutedEventArgs e)
        {
            RaiseEvent(new RoutedEventArgs(ChangeFontEvent, this));
        }

        private void ChatWindowGridSplitter_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (Math.Abs((this.AvalonConversation.VerticalOffset + this.AvalonConversation.ViewportHeight) - this.AvalonConversation.ExtentHeight) < Epsilon || this.AvalonConversation.ExtentHeight < this.AvalonConversation.ViewportHeight)
            {
                //this.AvalonConversation.ScrollToEnd();
                this.AvalonConversation.ScrollToLine(this.ChatDocument.LineCount);
            }
        }

        private void bnSendMessage_Click(object sender, RoutedEventArgs e)
        {
            string message = this.AvalonMessage.Text;
            if (!String.IsNullOrEmpty(message))
                SendMessage(message);
            this.AvalonMessage.Focus();
        }

        private void CommandBinding_PasteAndSend(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                string txt = Clipboard.GetText();
                if (String.IsNullOrEmpty(txt))
                    return;

                this.AvalonMessage.AppendText(txt);
                this.SendMessage(this.AvalonMessage.Text);
                this.AvalonMessage.Focus();
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error pasting clipboard", err);
            }
        }

        private void AvalonMessage_LostFocus(object sender, RoutedEventArgs e)
        {
            if (IsInputTextBlockFocused)
                IsInputTextBlockFocused = false;
        }

        private void AvalonMessage_GotFocus(object sender, RoutedEventArgs e)
        {
            if (IsInputTextBlockFocused)
                IsInputTextBlockFocused = true;
        }

        private void JiveChatControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            var textView = this.AvalonConversation.TextArea.TextView;
            double offset = textView.VerticalOffset;
            if ((offset + textView.ActualHeight + 1 >= textView.DocumentHeight) || e.PreviousSize.Height - e.NewSize.Height > 100)
                ForceScrollNextLineToEnd = true;
            _resizeTimer.IsEnabled = true;
            _resizeTimer.Stop();
            _resizeTimer.Start();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        private void JiveChatControl_Loaded(object sender, RoutedEventArgs e)
        {
            this.AvalonConversation.TextArea.TextView.ScrollOffsetChanged += this.TextView_ScrollOffsetChanged;
            this.SizeChanged += JiveChatControl_SizeChanged;
            _resizeTimer.Tick += _resizeTimer_Tick;
        }

        private void JiveChatControl_Unloaded(object sender, RoutedEventArgs e)
        {
            foreach (var commandBinding in this.AvalonMessage.TextArea.CommandBindings.Cast<CommandBinding>())
            {
                if (commandBinding.Command == ApplicationCommands.Copy)
                {
                    commandBinding.CanExecute -= CopyCommandBinding_CanExecute;
                    commandBinding.Executed -= CopyCommandBinding_PreviewExecuted;
                }
                else if (commandBinding.Command == ApplicationCommands.Cut)
                    commandBinding.CanExecute -= CopyCommandBinding_CanExecute;
                else if (commandBinding.Command == ApplicationCommands.Paste)
                    commandBinding.CanExecute -= PasteCommandBinding_CanExecute;
                else if (commandBinding.Command == ApplicationCommands.Delete)
                    commandBinding.CanExecute -= CopyCommandBinding_CanExecute;

            }

            foreach (var commandBinding in this.AvalonConversation.TextArea.CommandBindings.Cast<CommandBinding>())
            {
                if (commandBinding.Command == ApplicationCommands.Copy)
                {
                    commandBinding.CanExecute -= CopyCommandBinding_CanExecute;
                    commandBinding.Executed -= CopyCommandBinding_PreviewExecuted;
                }
            }
            this.SizeChanged -= JiveChatControl_SizeChanged;
            _resizeTimer.Tick -= _resizeTimer_Tick;
            this.AvalonConversation.TextArea.TextView.ScrollOffsetChanged -= this.TextView_ScrollOffsetChanged;
            this.EmoticonControl.Cleanup();
        }
        void _resizeTimer_Tick(object sender, EventArgs e)
        {
            _resizeTimer.IsEnabled = false;
            SetScrollbar();
            _resizeTimer.Stop();
        }



        private void SearchCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = !String.IsNullOrEmpty(e.Parameter as string);
        }

        private void CommandBinding_GoogleText(object sender, ExecutedRoutedEventArgs e)
        {
            string url = string.Format("https://www.google.no/search?q={0}", e.Parameter.ToString());
            System.Diagnostics.Process.Start(url);
        }

        private void CommandBinding_BingText(object sender, ExecutedRoutedEventArgs e)
        {
            string url = string.Format("http://www.bing.com/search?q={0}", e.Parameter.ToString());
            System.Diagnostics.Process.Start(url);
        }
        #endregion


    }
}
